use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::fs;
use std::str::FromStr;

fn parse_data() -> Vec<HashMap<String, String>> {
    let contents = fs::read_to_string("input.txt").unwrap();

    let mut passports = Vec::new();
    for section in contents.replace("\r", "").split("\n\n") {
        let mut passport = HashMap::new();
        for line in section.lines() {
            for field in line.split(" ") {
                let mut splitted = field.split(":").collect::<Vec<_>>();
                passport.insert(splitted[0].to_string(), splitted[1].to_string());
            }
        }
        passports.push(passport);
    }

    return passports;
}

fn is_valid_passport(passport: &HashMap<String, String>, fields: &[(&str, bool)]) -> bool {
    for (key_name, is_required) in fields {
        if *is_required && !passport.contains_key(*key_name) {
            return false;
        }
    }

    return true;
}

struct B {
    pub f: Box<dyn Fn(&str) -> bool>,
}

impl B {
    fn new<F>(f: F) -> B
    where
        F: Fn(&str) -> bool + 'static,
    {
        B { f: Box::new(f) }
    }
}

fn is_valid_passport_with_rules(
    passport: &HashMap<String, String>,
    fields: &[(&str, bool, B)],
) -> bool {
    for (key_name, is_required, rule) in fields {
        if *is_required
            && (!passport.contains_key(*key_name) || !(rule.f)(passport.get(*key_name).unwrap()))
        {
            //println!("check failed: \nkey {}, value {:?}", *key_name, passport.get(*key_name));
            return false;
        }
    }

    return true;
}

fn get_fields_definition() -> [(&'static str, bool, B); 8] {
    lazy_static! {
        static ref FOUR_DIGITS_REG: Regex = Regex::new(r"^(\d{4})$").unwrap();
        static ref HEIGHT_REG: Regex = Regex::new(r"(\d+)(cm|in)").unwrap();
        static ref COLOR_REG: Regex = Regex::new(r"#[0-9a-f]{6}").unwrap();
        static ref ECL_REG: Regex = Regex::new(r"(amb|blu|brn|gry|grn|hzl|oth)").unwrap();
        static ref NINE_DIGITS_REG: Regex = Regex::new(r"^(\d{9})$").unwrap();
    }
    let fields = [
        // key_name, is_required, match_function
        (
            "byr",
            true,
            B::new(|s| {
                if (FOUR_DIGITS_REG.is_match(s)) {
                    let n = s.parse::<i32>().unwrap();
                    n >= 1920 && n <= 2002
                } else {
                    false
                }
            }),
        ),
        (
            "iyr",
            true,
            B::new(|s| {
                if (FOUR_DIGITS_REG.is_match(s)) {
                    let n = s.parse::<i32>().unwrap();
                    n >= 2010 && n <= 2020
                } else {
                    false
                }
            }),
        ),
        (
            "eyr",
            true,
            B::new(|s| {
                if (FOUR_DIGITS_REG.is_match(s)) {
                    let n = s.parse::<i32>().unwrap();
                    n >= 2020 && n <= 2030
                } else {
                    false
                }
            }),
        ),
        (
            "hgt",
            true,
            B::new(|s| {
                if let Some(caps) = HEIGHT_REG.captures(s) {
                    let n = caps.get(1).unwrap().as_str().parse::<i32>().unwrap();
                    let unit = caps.get(2).unwrap();
                    if unit.as_str() == "cm" {
                        n >= 150 && n <= 193
                    } else {
                        n >= 59 && n <= 76
                    }
                } else {
                    false
                }
            }),
        ),
        ("hcl", true, B::new(|s| COLOR_REG.is_match(s))),
        ("ecl", true, B::new(|s| ECL_REG.is_match(s))),
        ("pid", true, B::new(|s| NINE_DIGITS_REG.is_match(s))),
        ("cid", false, B::new(|s| true)),
    ];

    fields
}

#[test]
fn test_fields_define() {
    let fields = get_fields_definition();
    let byr = &fields[0].2.f;
    let iyr = &fields[1].2.f;
    let eyr = &fields[2].2.f;
    let hgt = &fields[3].2.f;
    let hcl = &fields[4].2.f;
    let ecl = &fields[5].2.f;
    let pid = &fields[6].2.f;

    assert!(byr("2002"));
    assert!(!byr("2003"));

    assert!(hgt("60in"));
    assert!(hgt("190cm"));
    assert!(!hgt("190in"));
    assert!(!hgt("190"));

    assert!(hcl("#123abc"));
    assert!(!hcl("#123abz"));
    assert!(!hcl("123abc"));

    assert!(ecl("brn"));
    assert!(!ecl("wat"));

    assert!(pid("000000001"));
    assert!(!pid("0123456789"));
}

fn part2() {
    println!("Part 2:");
    let passports = parse_data();
    //println!("{:?}", &passports);
    //println!("has ecl: {}", passports[1].contains_key("ecl"));
    let fields = get_fields_definition();
    let mut valid_count = 0;
    for passport in &passports {
        if is_valid_passport_with_rules(passport, &fields) {
            valid_count += 1;
        }
    }

    println!("Valid count: {}", valid_count);
}

fn part1() {
    println!("Part 1:");
    let passports = parse_data();
    let fields = [
        // key_name, is_required
        ("byr", true),
        ("iyr", true),
        ("eyr", true),
        ("hgt", true),
        ("hcl", true),
        ("ecl", true),
        ("pid", true),
        ("cid", false),
    ];
    //println!("{:?}", &passports);
    //println!("has ecl: {}", passports[1].contains_key("ecl"));

    let mut valid_count = 0;
    for passport in &passports {
        if is_valid_passport(passport, &fields) {
            valid_count += 1;
        }
    }

    println!("Valid count: {}", valid_count);
}

fn main() {
    part1();
    part2();
}
